我们知道,如果我们实现了一个 Activity
,那么就必须在 your AndroidManifest.xml 中申明,否则会报 android.content.ActivityNotFoundException: Unable to find explicit activity class {XXXXXX}; have you declared this activity in your AndroidManifest.xml?
的错误。
下面介绍的的动态注册组件就是为了绕开这个机制。当然,如果你在宿主的 AndroidManifest.xml 里面把插件需要的 Activity
都申明一下,那么下面的工作就完全不用做了。
动态注册组件原理
动态的注册组件就是我们常说的Hook技术。
想要了解插件化的Hook技术我们需要先了解一下 Activity
的启动流程,Activity
的启动流程要涉及到App进程以及system_server进程,system_server进程的AMS负责 Activity
的真实性校验以及生命周期管理,App进程负责创建 Activity
对象以及回调生命周期的方法。
由于Activity
的检验过程是在AMS进程完成的,我们对system_server进程里面的操作无能为力,只有在我们APP进程里面执行的过程才是有可能被Hook掉的,因此,所有的Hook我们只能在App进程完成,那么在AMS进程里面进行校验的 Activity
也必须是真实存在的。
因此,Hook的基本思路就是当调用AMS时,就用我们真实注册的存在的 Activity
信息(对应上一篇文章的AndroidManifest.xml中的A、A1、A2….A33等 Activity
),AMS回调到App进程时替换为插件中需要启动的 Activity
信息,从而达到欺骗系统的目的。
可以先看一下我的博客startActivity 流程,通过看启动流程图我们可以清晰的看到,涉及到 Acitivity
的AMS进程和App进程的边界操作有两个:startActivity和对LAUNCH_ACTIVITY消息的处理,这也就是我们需要Hook的两个重要点。
下面通过实际代码来进程介绍。
前面我们说过,替换 Instrumentation
对象和 ActivityThreadHandlerCallback
是插件化工作中的重头戏。这里用到了我们说的“Hook”技术。Instrumentation
类看一下官方文档对这个类的解释,该类跟踪 Application
及 Activity
的整个生命周期,它的一些方法在 Application
及 Activity
所有生命周期函数的调用中,都会先调用这些方法。
熟悉 Activity
启动流程的同学都知道,启动 Activity
是由 Activity
的 startActivityForResult()
方法启动,通过 Instrumentation
的 execStartActivity
方法激活生命周期。
1 | public void startActivityForResult(Intent intent, int requestCode, @Nullable Bundle options) { |
Activity
的实例化在 ActivityThread
的 performLaunchActivity()
方法中通过 Instrumentation
的 newActivity()
方法实例化。
1 | private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) { |
‘onCreate()’ 生命周期函数的调用也是在 ActivityThread.performLaunchActivity()
中调用 Instrumentation
的 callActivityOnCreate()
方法来实现的。
1 | private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) { |
因此,得到了这个对象,我们就可以进入并跟踪 Application
和 Activity
的生命周期流程。
Small 想要做到动态注册 Activity
,首先在宿主 Manifest 中注册一个命名特殊的占坑 Activity
来欺骗 startActivityForResult
以获得生命周期,再欺骗 performLaunchActivity
来获得插件 Activity
实例,又为了处理之间的信息传递,因此有了后面的 ActivityThreadHandlerCallback
。
接下来我们就在 ApkBundleLauncher.InstrumentationWrapper
来看一下这些是如何实现的。
先来看一下 execStartActivity
方法:execStartActivity
方法有两个实现,一个是API Level 20以前的,一个是API Level 20以后的,仅仅是参数不同而已。
由于前面我们用 ApkBundleLauncher.InstrumentationWrapper
替换了 mInstrumentation
,因此会调用到 ApkBundleLauncher.InstrumentationWrapper
中的 execStartActivity()
方法。该方法做的工作后面再详细介绍。主要是把需要启动的真实 Activity
替换为占坑 Activity
。
熟悉 Activity
流程的同学都知道,真正启动 Activity
时,ActivityManagerService
调用 ApplicationThread.scheduleLaunchActivity
接口,通知相应的进程执行启动 Activity
的操作,ApplicationThread
把这个启动 Activity
的操作转发给 ActivityThread
,ActivityThread
通过 ClassLoader
导入相应的 Activity
类,然后把它启动。
具体的在 ActivityThread.ApplicationThread.scheduleLaunchActivity
方法中会调用 sendMessage(H.LAUNCH_ACTIVITY, r)
,该消息由 ActivityThread
中的消息处理对象 mH
来处理,由于我们把 mH
的 mCallback
替换为了ActivityThreadHandlerCallback
,因此也会对 LAUNCH_ACTIVITY
消息进行拦截处理,处理完后再由mH
来处理正常的流程。
这里解释一下为什么要调用 ensureInjectMessageHandler
来反射替换 ActivityThread
的 Handler
对象 mH
的 mCallback
呢?
先看一下 Handler
源码中的 dispatchMessage()
方法。
1 | /** |
这里会先执行 mCallback
的 handleMessage()
,在返回false的情况下再执行自身的 handleMessage()
。
这样就可以在 ActivityThreadHandlerCallback
中处理一些事情,然后在调用 mH
的方法。
启动插件Activity流程
这里以 ApkBundleLauncher
来作为 Bundle
的 BundleLauncher
为例进行说明。
1 | ├── Small.openUri() |
根据URI匹配Bundle
匹配插件中注册的 Activity
1 | protected static Bundle getLaunchableBundle(Uri uri) { |
BundleLauncher.launchBundle()
启动 Activity
1
2
3
4
5
6
7
8
9
10
11
12
13
14public void launchBundle(Bundle bundle, Context context) {
if(bundle.isLaunchable()) {
if(context instanceof Activity) {
Activity activity = (Activity)context;
if(this.shouldFinishPreviousActivity(activity)) {
activity.finish();
}
activity.startActivityForResult(bundle.getIntent(), 10000);
} else {
context.startActivity(bundle.getIntent());
}
}
}
启动真实的Activity
1 | public void prelaunchBundle(Bundle bundle) { |
启动流程拦截,启动占坑位Activity
1 | protected static class InstrumentationWrapper extends Instrumentation |
消息拦截
由于前面介绍的反射替换 ActivityThread
的 mH
对象的 mCallback
,这里拦截了四种消息:
1 | private static class ActivityThreadHandlerCallback implements Handler.Callback { |
LAUNCH_ACTIVITY
对 LAUNCH_ACTIVITY
的处理主要是将启动的占坑位的 Activity
重新替换为真实的 Activity
1 |
|
拦截OnCreate方法
对 Activity
的 OnCreate
调用的拦截由 InstrumentationWrapper.callActivityOnCreate()
来进行处理。
1 |
|
另外还有对 OnStop
,OnDestroy
等其他生命周期方法的拦截,这里就不一一介绍了。